package com.sky.service.impl;

import com.sky.dto.GoodsSalesDTO;
import com.sky.entity.Orders;
import com.sky.mapper.OrderMapper;
import com.sky.mapper.UserMapper;
import com.sky.service.ReportService;
import com.sky.service.WorkspaceService;
import com.sky.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class ReportServiceImpl implements ReportService {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private WorkspaceService workspaceService;

    /**
     * 营业额统计
     * @param begin 营业额统计开始时间
     * @param end 营业额统计结束时间
     * @return 营业额统计结果
     */
    @Override
    public TurnoverReportVO getTurnoverStatistics(LocalDate begin, LocalDate end) {
        // 1. 创建日期字符串, 用逗号分隔, 例如: 2022-10-01,2022-10-02,2022-10-03
        // 1.1 创建日期集合, 存放从开始到结束的每一天日期
        List<LocalDate> dateList = getDateList(begin, end);
        // 1.2 创建日期字符串, 用逗号分隔
        String DateString = StringUtils.join(dateList, ",");

        // 2. 创建营业额字符串, 用逗号分隔, 例如: 406.0,1520.0,75.0
        // 2.1 创建营业额集合: 存放从开始到结束日期的每一天营业额
        List<Double> turnoverList = new ArrayList<>();
        for (LocalDate localDate : dateList) {
            // 2.2 获取当前日期对应的营业额: 营业额: 状态为已完成的对应日期订单的总金额
            // 方式一: 这种方式更简洁, 但不够精确
            // select sum(amount) from orders where order_time like '%' + localDate + '%' and status = 5
            // 方式二: 由于下单时间是LocalDateTime类型(与数据库datetime类型兼容), 而这里的传过来的参数时间是LocalDate类型, 所以需要将LocalDate转为LocalDateTime。 第一个? 2022-10-01 00:00:00(beginTime), 第二个? 2022-10-01 23:59:59(endTime)
            // select sum(amount) from orders where order_time > beginTime and order_time < endTime and status = 5
            LocalDateTime beginTime = LocalDateTime.of(localDate, LocalTime.MIN); // 获取当天00:00:00
            LocalDateTime endTime = LocalDateTime.of(localDate, LocalTime.MAX); // 获取当天23:59:59
            Map<String, Object> map = new HashMap<>();
            map.put("begin", beginTime);
            map.put("end", endTime);
            map.put("status", Orders.COMPLETED);
            Double turnover = orderMapper.sumByMap(map);
            // 2.3 添加到营业额集合中
            turnoverList.add(turnover == null ? 0.0 : turnover);
        }
        // 2.4 创建营业额字符串, 用逗号分隔
        String turnoverString = StringUtils.join(turnoverList, ",");

        // 3. 封装返回结果
        return TurnoverReportVO.builder().dateList(DateString).turnoverList(turnoverString).build();
    }

    /**
     * 用户统计
     * @param begin 用户统计开始时间
     * @param end 用户统计结束时间
     * @return 用户统计结果
     */
    @Override
    public UserReportVO getUserStatistics(LocalDate begin, LocalDate end) {
        // 1. 创建日期字符串, 用逗号分隔
        // 1.1 创建日期集合, 存放从开始到结束的每一天日期
        List<LocalDate> dateList = getDateList(begin, end);
        // 1.2 创建日期字符串, 用逗号分隔
        String dateString = StringUtils.join(dateList, ",");

        // 2. 创建总用户数量字符串和新增用户数量字符串, 用逗号分隔
        // 2.1 创建新增用户数量集合和总用户数量集合: 存放从开始到结束的每一天的新增用户数量和总用户数量
        List<Integer> totalUserList = new ArrayList<>(); // select count(id) from user where create_time < endTime
        List<Integer> newUserList = new ArrayList<>(); // select count(id) from user where create_time > beginTime and create_time < endTime
        // 2.2 获取每一天新增用户数量和总用户数量, 封装到集合中
        for (LocalDate localDate : dateList) {
            LocalDateTime beginTime = LocalDateTime.of(localDate, LocalTime.MIN);
            LocalDateTime endTime = LocalDateTime.of(localDate, LocalTime.MAX);
            // 2.2.1 获取当前日期对应的总用户数量: 总用户数量: 创建时间小于当前日期最大时间的用户数量
            Map<String, Object> map = new HashMap<>();
            map.put("end", endTime);
            Integer totalCount = userMapper.countByMap(map);
            totalUserList.add(totalCount);
            // 2.2.2 获取当前日期对应的新增用户数量: 新增用户数量: 创建时间在当前日期最小时间到当前日期最大时间的用户数量
            map.put("begin", beginTime);
            Integer newCount = userMapper.countByMap(map);
            newUserList.add(newCount);
        }
        // 2.3 创建总用户数量字符串和新增用户数量字符串, 用逗号分隔
        String newUserListString = StringUtils.join(newUserList, ",");
        String totalUserListString = StringUtils.join(totalUserList, ",");

        // 3. 封装返回结果
        return UserReportVO.builder().dateList(dateString).newUserList(newUserListString).totalUserList(totalUserListString).build();
    }

    /**
     * 订单统计
     * @param begin 订单开始时间
     * @param end 订单结束时间
     * @return 订单统计结果
     */
    @Override
    public OrderReportVO getOrdersStatistics(LocalDate begin, LocalDate end) {
        // 创建存放每一天日期的集合
        List<LocalDate> dateList = getDateList(begin, end);

        // 创建存放每一天的订单总数和有效订单数的集合
        List<Integer> orderCountList = new ArrayList<>();
        List<Integer> validOrderCountList = new ArrayList<>();

        // 遍历日期集合, 获取每一天的有效订单数和订单总数, 封装到相应的订单数集合中
        for (LocalDate localDate : dateList) {
            LocalDateTime beginTime = LocalDateTime.of(localDate, LocalTime.MIN);
            LocalDateTime endTime = LocalDateTime.of(localDate, LocalTime.MAX);
            // 获取每一天订单总数 select count(id) from orders where order_time > beginTime and order_time < endTime
            Integer orderCount = count(beginTime, endTime,  null);
            // 获取每一天有效(已完成)订单数 select count(id) from orders where order_time > beginTime and order_time < endTime and status = 5
            Integer validOrderCount = count(beginTime, endTime, Orders.COMPLETED);
            orderCountList.add(orderCount);
            validOrderCountList.add(validOrderCount);
        }
        // 获得日期区间内的订单总数和有效订单数
        Integer totalOrderCount = orderCountList.stream().reduce(Integer::sum).orElse(0); // orElse(0): 如果列表为空，则返回0
        Integer validOrderCount = validOrderCountList.stream().reduce(Integer::sum).orElse(0);
        // 获取日期区间内的订单完成率
        double orderCompletionRate = 0.0;
        if (totalOrderCount != 0) {
            orderCompletionRate = validOrderCount.doubleValue() / totalOrderCount.doubleValue();
        }
        // 构建返回结果
        return OrderReportVO.builder()
                .dateList(StringUtils.join(dateList, ","))
                .orderCountList(StringUtils.join(orderCountList, ","))
                .validOrderCountList(StringUtils.join(validOrderCountList, ","))
                .totalOrderCount(totalOrderCount)
                .validOrderCount(validOrderCount)
                .orderCompletionRate(orderCompletionRate)
                .build();
    }

    /**
     * 统计指定时间区间内指定状态订单数量
     * @param begin 开始时间
     * @param end 结束时间
     * @param status 订单状态
     * @return 订单数量
     */
    private Integer count(LocalDateTime begin, LocalDateTime end, Integer status) {
        Map<String, Object> map = new HashMap<>();
        map.put("begin", begin);
        map.put("end", end);
        map.put("status", status);
        return orderMapper.countByMap(map);
    }

    /**
     * 获取指定时间区间内的日期列表
     * @param begin 开始时间
     * @param end 结束时间
     * @return 日期列表
     */
    private static List<LocalDate> getDateList(LocalDate begin, LocalDate end) {
        List<LocalDate> dateList = new ArrayList<>();
        dateList.add(begin);
        while (!begin.equals(end)) {
            begin = begin.plusDays(1); // 日期加1天
            dateList.add(begin);
        }
        return dateList;
    }

    /**
     * 销量前10排名
     * @param begin 订单开始时间
     * @param end 订单结束时间
     * @return 销量排名结果
     */
    @Override
    public SalesTop10ReportVO getSalesTop10(LocalDate begin, LocalDate end) {
        // 1. 获取指定时间区间内，商品top10列表
        LocalDateTime beginTime = LocalDateTime.of(begin, LocalTime.MIN);
        LocalDateTime endTime = LocalDateTime.of(end, LocalTime.MAX);
        List<GoodsSalesDTO> salesTop10 = orderMapper.getSalesTop10(beginTime, endTime);

        // 2. 获取商品名称列表
        List<String> name = salesTop10.stream().map(GoodsSalesDTO::getName).toList();
        String nameList = StringUtils.join(name, ",");

        // 3. 获取商品销量列表
        List<Integer> number = salesTop10.stream().map(GoodsSalesDTO::getNumber).toList();
        String numberList = StringUtils.join(number, ",");

        // 4. 封装返回结果
        return SalesTop10ReportVO.builder().nameList(nameList).numberList(numberList).build();
    }

    /**
     * 导出营业数据
     * @param response HttpServletResponse对象
     */
    @Override
    public void exportBusinessData(HttpServletResponse response) {
        // 1. 查询数据库，获取营业数据 -- 最近30天的营业数据
        LocalDate beginDate = LocalDate.now().minusDays(30);
        LocalDate endDate = LocalDate.now().minusDays(1);
        BusinessDataVO businessData = workspaceService.getBusinessData(LocalDateTime.of(beginDate, LocalTime.MIN), LocalDateTime.of(endDate, LocalTime.MAX)); // 概览数据

        try (InputStream in = getClass().getClassLoader().getResourceAsStream("template/运营数据报表模板.xlsx")) {
            // 2. 通过POI工具把数据写入到Excel文件中
            // 基于模板文件创建新的文件
            assert in != null;
            XSSFWorkbook excel = new XSSFWorkbook(in);
            XSSFSheet sheet = excel.getSheetAt(0); // 获取第一个工作簿
            // a. 概览数据
            // 第二行
            // 2.1 填充时间范围
            XSSFRow row = sheet.getRow(1); // 获取第二行
            XSSFCell cell = row.getCell(1); // 获取第二个单元格(单元格合并, 按照大格算)
            cell.setCellValue("时间: " + beginDate + " 至 " + endDate);

            // 第四行
            // 2.2 填充营业额
            row = sheet.getRow(3); // 获取第四行
            cell = row.getCell(2); // 获取第三个单元格
            cell.setCellValue(businessData.getTurnover()); // 营业额
            // 2.3 填充订单完成率
            cell = row.getCell(4);
            cell.setCellValue(businessData.getOrderCompletionRate());
            // 2.4 填充新增用户数
            row.getCell(6).setCellValue(businessData.getNewUsers());

            // 第五行
            row = sheet.getRow(4);
            // 2.5 填充有效订单数
            row.getCell(2).setCellValue(businessData.getValidOrderCount());
            // 2.6 填充平均客单价
            row.getCell(4).setCellValue(businessData.getUnitPrice());

            // b. 明细数据
            for (int i = 0; i < 30; i++) { // 循环30天
                LocalDate localDate = beginDate.plusDays(i);
                // 获取某一天的营业数据
                BusinessDataVO someDayBusinessData = workspaceService.getBusinessData(LocalDateTime.of(localDate, LocalTime.MIN), LocalDateTime.of(localDate, LocalTime.MAX));
                // 获得某一行
                row = sheet.getRow(7 + i);
                // 填充这一行数据
                row.getCell(1).setCellValue(localDate.toString());
                row.getCell(2).setCellValue(someDayBusinessData.getTurnover());
                row.getCell(3).setCellValue(someDayBusinessData.getValidOrderCount());
                row.getCell(4).setCellValue(someDayBusinessData.getOrderCompletionRate());
                row.getCell(5).setCellValue(someDayBusinessData.getUnitPrice());
                row.getCell(6).setCellValue(someDayBusinessData.getNewUsers());
            }

            // 3. 通过输出流把Excel文件下载到客户端浏览器
            ServletOutputStream out = response.getOutputStream();
            excel.write(out); // 把内存中的excel文件写回到输出流中(即客户端浏览器会自动下载该文件到下载目录, 文件名不变)
        } catch (IOException e) {
            log.error("读取Excel模板文件失败", e);
        }
    }
}
